package org.yaac.server.egql;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Sets.newHashSet;

import java.util.List;
import java.util.Set;

import org.yaac.server.egql.exception.EGQLE050Exception;
import org.yaac.server.egql.exception.EGQLE051Exception;
import org.yaac.server.egql.exception.EGQLE052Exception;
import org.yaac.server.egql.exception.EGQLException;
import org.yaac.server.egql.processor.ChannelMsgSender;
import org.yaac.server.egql.processor.InsertProcessor;
import org.yaac.server.egql.processor.Processor;
import org.yaac.shared.SharedConstants;
import org.yaac.shared.egql.EGQLConstant;

import com.google.common.base.Objects;

/**
 * @author Max Zhu (thebbsky@gmail.com)
 *
 */
public class InsertStatement extends Statement {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private String kind;
	
	private List<InsertItem> items;
	
	private SelectStatement selectStmt;
	
	public InsertStatement() {
		super();
		this.items = newLinkedList();
	}

	public String getKind() {
		return kind;
	}

	public InsertStatement withKind(String kind) {
		this.kind = kind;
		return this;
	}
	
	public InsertStatement withItem(InsertItem item) {
		this.items.add(item);
		return this;
	}
	
	public InsertStatement withSelectStatement(SelectStatement stmt) {
		this.selectStmt = stmt;
		return this;
	}
	
	/**
	 * @return keyItem, if any
	 */
	public InsertItem keyItem() {
		for (InsertItem item : items) {
			if (SharedConstants.Datastore.KEY_RESERVED_NAME.equals(item.getIdentity())) {
				return item;
			}
		}
		return null;
	}
	
	/**
	 * @return
	 */
	public Iterable<InsertItem> propertyItems() {
		List<InsertItem> properties = newArrayList(items);
		properties.remove(keyItem());
		return properties;
	}

	@Override
	public void validate() throws EGQLException {
		Set<String> itemNames = newHashSet();
		for (InsertItem item : items) {
			// duplicate insert item is not allowed
			if (itemNames.contains(item.getIdentity())) {
				throw new EGQLE050Exception();
			} else {
				itemNames.add(item.getIdentity());
			}
			
			// aggregation function is not allowed in insert statement without select clause
			if (!item.getE().aggregationChildren().isEmpty() && this.selectStmt == null) {
				throw new EGQLE051Exception();
			}
			
			// field is not allowed in insert statement without select clause
			if (!item.getE().nonAggregationProperties().isEmpty() && this.selectStmt == null) {
				throw new EGQLE052Exception();
			}
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof InsertStatement) {
			return Objects.equal(this.kind, ((InsertStatement) obj).kind) 
				&& Objects.equal(this.items, ((InsertStatement) obj).items)
				&& Objects.equal(this.selectStmt, ((InsertStatement) obj).selectStmt);
		} 
		
		return false;
	}

	@Override
	public int hashCode() {
		return Objects.hashCode(kind, selectStmt);
	}
	
	@Override
	public String toString() {
		return Objects.toStringHelper(this)
				.add("kind", kind)
				.add("items", items)
				.add("selectStmt", selectStmt)
				.toString();
	}

	@Override
	public List<Processor> generateProcessors() {
		Processor datastoreLoader = this.selectStmt.datastoreLoader();
		Processor selector = this.selectStmt.selector();
		Processor inserter = new InsertProcessor(this);
		Processor msgSender = new ChannelMsgSender(EGQLConstant.DEFAULT_MAX_RESULT);
		
		return newArrayList(datastoreLoader, selector, inserter, msgSender);
	}

	@Override
	public boolean isSimpleStatement() {
		// if there is a select statement, then execute as processors, otherwise simply insert
		return this.selectStmt == null;
	}
}
